library SortUtils
//===========================================================================
// Information:
//==============
//
//      SortUtils allows you sort arrays of reals, units and structs efficiently.
//  Many sorting methods were benchmarked, and a QuickSort/InsertionSort hybrid
//  was found to be the most efficient for general purposes. You can sort lists
//  that are several hundred values long without any issues.
//
//===========================================================================
// How to use SortUtils dynamic arrays:
//======================================
//
//      JASS does not natively support dynamic arrays or array pointers. vJASS
//  supports them, but this library does not use those. It uses customized ones
//  that behave a bit differently. How to use them is shown below:
//
//      local RealArray ra = RealArray.create(size)
//      local UnitArray ua = UnitArray.create(size)
//      local StructArray sa = StructArray.create(size)
//
//      When creating an array, you must specify the size you intend to utilize.
//  For example, if ra[0...99] were utilized, the size should be set to 100. The
//  size can go as high as MaxArraySize. When you sort an array, only the indexes
//  within 0...size - 1 will be sorted, so make sure it is accurate. If you want
//  to alter the size after creating an array, you can do so using the .size var-
//  iable. For example: set ra.size = ra.size + 1
//
//      You must use .destroy() on your arrays as well, or you will quickly run
//  out of instances. You can have (8190 / MaxArraySize) simultaneous instances
//  of each array type. When you use .destroy(), all the values up to .size in
//  the array will be zeroed or nulled, so you don't need to worry about non-
//  zero values in newly created arrays, or reference leaks with UnitArrays.
//
//===========================================================================
// How to use SortUtils:
//======================
//
//      SortUtils includes the standard capability to sort a RealArray from least
//  to greatest. (If you want greatest to least, just read the array backwards
//  starting from index .size - 1). You can also sort a UnitArray or StructArray
//  according to a parallel RealArray. This is easy to understand with an example:
//
//  Before sorting:
//  UnitArray:      [Paladin][Archmage][Rifleman][Footman]
//  RealArray:      [ 500.0 ][ 1750.0 ][ 1250.0 ][ 250.0 ]
//
//  After sorting:
//  UnitArray:      [Footman][Paladin][Rifleman][Archmage]
//  RealArray:      [ 250.0 ][ 500.0 ][ 1250.0 ][ 1750.0 ]
//
//      As the values contained in the RealArray were sorted from least to greatest,
//  the same swaps were made on the values in the UnitArray. The result is that the
//  UnitArray is ordered as well. The values in the RealArray may represent anything,
//  such as the distance to a specific point, the current life of a unit, "threat
//  values" in an aggro system, etc.
//
//      However, there is an easier way to sort UnitArrays/StructArrays than using
//  a parallel RealArray, and that is with a SortFunc. A SortFunc takes a unit or
//  struct and returns a real. You can use these functions to specify whatever
//  criteria you wish to use to sort a UnitArray or StructArray by. Example:
//
//  function UnitDistance takes unit u returns real
//      return SquareRoot(Pow(GetUnitX(u) - PointX, 2.) + Pow(GetUnitY(u) - PointY, 2.))
//  endfunction
//
//  function Example takes nothing returns nothing
//      local UnitArray ua = UnitArray.create(4)
//      local RealArray ra
//      local integer i
//           set ua[0] = Paladin
//           set ua[1] = Archmage
//           set ua[2] = Rifleman
//           set ua[3] = Footman
//           set PointX = -423.0 //Transfering data with globals, just like a ForGroup.
//           set PointY = 624.0
//           call SortUnits(ua, UnitDistance) //The units are now sorted according to their
//                                            //respective distances from PointX, PointY.
//           set ra = GetSortedValues()  //This will return a sorted RealArray containing the
//                                       //distances calculated for each of the units.
//           set i = ra.size - 1 //Reading through the lists backwards- farthest to nearest.
//           loop
//               exitwhen i < 0
//               call BJDebugMsg(GetUnitName(ua[i])+" is "+R2S(ra[i])+" distance away.")
//               set i = i - 1
//           endloop
//  endfunction
//
//===========================================================================
// SortUtils API:
//================
//
//  RealArray.create(size) -> RealArray
//    Create a dynamic real array.
//
//  UnitArray.create(size) -> UnitArray
//    Create a dynamic unit array.
//
//  StructArray.create(size) -> StructArray
//    Create a dynamic struct (integer) array. You should not mix different
//    types of structs within the same StructArray.
//
//  .size -> integer
//    You can alter the size of a dynamic array after creating it. Only indexes
//    up to .size - 1 will be sorted.
//
//  .destroy()
//    You must destroy dynamic arrays when you're done with them. Indexes up to
//    .size - 1 will be automatically zeroed or nulled.
//
//  SortValues(RealArray)
//    Sort the values contained in a RealArray from least to greatest. Read it
//    backwards starting from index .size - 1 if you want greatest to least.
//
//  SortUnitsByValues(UnitArray, RealArray)
//    Sorts the values contained in a UnitArray according to the values contained
//    in a parallel RealArray. As the RealArray is sorted, the same swaps are
//    performed on the UnitArray.
//
//  SortStructsByValues(StructArray, RealArray)
//    Same as above, but sorts a StructArray rather than a UnitArray.
//
//  SortUnits(UnitArray, SortUnitFunc)
//    Sorts a UnitArray by whatever criteria is specified in the SortUnitFunc.
//    A SortUnitFunc must take a unit and return a real.
//
//  SortStructs(StructArray, SortStructFunc)
//    Sorts a StructArray by whatever criteria is specified in the SortStructFunc.
//    A SortStructFunc must take the same kind of struct stored in the StructArray
//    and return a real.
//
//  GetSortedValues() -> RealArray
//    After using SortUnits or SortStructs, this function will return a RealArray
//    containing the ordered values that the units or structs were sorted by.
//
//  Group2UnitArray(group) -> UnitArray
//    This will convert a group into a UnitArray. The group remains unchanged. If
//    you want to sort the contents of a unitgroup, use this to convert it to a
//    UnitArray, which you can then use other SortUtils functions to sort.
//
///===========================================================================
// SortUtils textmacro:
//======================
//
//     If you intend to use SortUtils with a library that makes heavy use of
// dynamic arrays and you are worried about instance limits, a special text-
// macro has been provided to solve that problem. If you use this textmacro
// within a scope or library, the RealArrays/UnitArrays/StructArrays used
// within that scope or library will magically have their own instance limit.
// However, arrays created within the scope or library cannot be used outside
// of it.
//
//     The textmacro must be placed above any calls to the SortUtils API.
// Also, you will be required to declare a private constant integer named
// MaxArraySize within that scope or library, which will be used within it
// rather than the MaxArraySize defined within SortUtils.
//
//     To use the textmacro, type: //! runtextmacro SortUtils("private")
//
///===========================================================================
// Configuration:
//================

globals
    public constant integer InsertionSortThreshold = 16
    //Sublists with size <= InsertionSortThreshold will be sorted with
    //InsertionSort rather than QuickSort. Benchmarking showed that values
    //between 10 and 20 produced the greatest speed-up.
    private constant integer MaxArraySize = 100
    //The higher this is set, the fewer instances will be available of each
    //dynamic array type. Setting it higher than 500 is probably useless
    //since sorting lists that large may hit the op-limit.
    public integer Values = 0
    //
endglobals


function interface SortUnitFunc takes unit u returns real
function interface SortStructFunc takes integer i returns real

function GetSortedValues takes nothing returns integer
    local integer values = Values
        set Values = 0
    return values
endfunction

//! textmacro SortUtils takes SIZE
private struct RealArray {
    real values[$SIZE$];
    integer size = 0;

    static method create(integer size) -> RealArray {
        RealArray this;
        if (size > $SIZE$) {
            BJDebugMsg("SortUtils error: Attempted to create RealArray with size larger than "+I2S($SIZE$));
            return 0;
        }
        this = thistype.allocate();
        this.size = size;
        return this;
    }
    method operator []=(integer i, real value) {
        this.values[i] = value;
    }
    method operator [](integer i) -> real {
        return this.values[i];
    }
    method destroy() {
        integer i;
        for (i=this.size - 1; i>=0; i-=1) {
            this.values[i]=0.00;
        }
        this.size = 0;
        this.deallocate();
    }
}

private struct UnitArray {
    unit values[$SIZE$];
    integer size = 0;

    static method create(integer size) -> UnitArray {
        UnitArray this;
        if (size > $SIZE$) {
            BJDebugMsg("SortUtils error: Attempted to create UnitArray with size larger than "+I2S($SIZE$)+".");
            return 0;
        }
        this = thistype.allocate();
        this.size = size;
        return this;
    }
    method operator []=(integer i, unit value) {
        this.values[i] = value;
    }
    method operator [](integer i) -> unit {
        return this.values[i];
    }
    method destroy() {
        integer i;
        for (i=this.size - 1; i>=0; i-=1) {
            this.values[i]=null;
        }
        this.size = 0;
        this.deallocate();
    }
}

private struct StructArray {
    integer values[$SIZE$];
    integer size = 0;

    static method create(integer size) -> StructArray {
        StructArray this;
        if (size > $SIZE$) {
            BJDebugMsg("SortUtils error: Attempted to create StructArray with size larger than "+I2S($SIZE$)+".");
            return 0;
        }
        this = thistype.allocate();
        this.size = size;
        return this;
    }
    method operator []=(integer i, integer value) {
        this.values[i] = value;
    }
    method operator [](integer i) -> integer {
        return this.values[i];
    }
    method destroy() {
        integer i;
        for (i=this.size - 1; i>=0; i-=1) {
            this.values[i]=0;
        }
        this.size = 0;
        this.deallocate();
    }
}

private function SortValues_Sub(RealArray values, integer left, integer right) {
    integer pivot = GetRandomInt(left, right);
    real value = values[pivot];
    integer i = left;
    integer j = left;
    real swap;
        values[pivot] = values[right];
        values[right] = value;
        for (j=left; j<right; j+=1) {
            if (values[j]<value) {
                swap = values[j];
                values[j] = values[i];
                values[i] = swap;
                i+=1;
            }
        }
        values[right] = values[i];
        values[i] = value;
        if (right - (i + 1) > SortUtils_InsertionSortThreshold) {
            SortValues_Sub(values, i + 1, right);
        }
        if ((i - 1) - left > SortUtils_InsertionSortThreshold) {
            SortValues_Sub(values, left, i - 1);
        }
}

private function SortValues(RealArray values) {
    integer left = 1;
    integer right = values.size - 1;
    real value;
    integer j;
        SortValues_Sub(values, 0, right);
        for (left=1; left<=right; left+=1) {
            value = values[left];
            for (j=left - 1; j >= 0 && values[j] >= value; j-=1) {
                values[j + 1] = values[j];
            }
            values[j + 1] = value;
        }
}

private function SortUnitsByValues_Sub(UnitArray units, RealArray values, integer left, integer right) {
    integer pivot = GetRandomInt(left, right);
    real value = values[pivot];
    unit u = units[pivot];
    integer i = left;
    integer j = left;
    real swap;
    unit swapu;
        values[pivot] = values[right];
        units[pivot] = units[right];
        values[right] = value;
        units[right] = u;
        for (j=left; j<right; j+=1) {
            if (values[j] < value) {
                swap = values[j];
                swapu = units[j];
                values[j] = values[i];
                units[j] = units[i];
                values[i] = swap;
                units[i] = swapu;
                i = i + 1;
            }
        }
        values[right] = values[i];
        units[right] = units[i];
        values[i] = value;
        units[i] = u;
        if (right - (i + 1) > SortUtils_InsertionSortThreshold) {
            SortUnitsByValues_Sub(units, values, i + 1, right);
        }
        if ((i - 1) - left > SortUtils_InsertionSortThreshold) {
            SortUnitsByValues_Sub(units, values, left, i - 1);
        }
    u = null;
    swapu = null;
}

private function SortUnitsByValues(UnitArray units, RealArray values) {
    integer left = 1;
    integer right = values.size - 1;
    real value;
    unit u;
    integer j;
        SortUnitsByValues_Sub(units, values, 0, right);
        for (left=1; left<=right; left+=1) {
            value = values[left];
            u = units[left];
            for (j=left - 1; j >= 0 && values[j] >= value; j-=1) {
                values[j + 1] = values[j];
                units[j + 1] = units[j];
            }
            values[j + 1] = value;
            units[j + 1] = u;
        }
    u=null;
}

private function SortStructsByValues_Sub(StructArray structs, RealArray values, integer left, integer right) {
    integer pivot = GetRandomInt(left, right);
    real value = values[pivot];
    integer s = structs[pivot];
    integer i = left;
    integer j = left;
    real swap;
    integer swaps;
        values[pivot] = values[right];
        structs[pivot] = structs[right];
        values[right] = value;
        structs[right] = s;
        for (j=left; j<right; j+=1) {
            if (values[j] < value) {
                swap = values[j];
                swaps = structs[j];
                values[j] = values[i];
                structs[j] = structs[i];
                values[i] = swap;
                structs[i] = swaps;
                i = i + 1;
            }
        }
        values[right] = values[i];
        structs[right] = structs[i];
        values[i] = value;
        structs[i] = s;
        if (right - (i + 1) > SortUtils_InsertionSortThreshold) {
            SortStructsByValues_Sub(structs, values, i + 1, right);
        }
        if ((i - 1) - left > SortUtils_InsertionSortThreshold) {
            SortStructsByValues_Sub(structs, values, left, i - 1);
        }
}

private function SortStructsByValues(StructArray structs, RealArray values) {
    integer left = 1;
    integer right = values.size - 1;
    real value;
    integer s;
    integer j;
        SortStructsByValues_Sub(structs, values, 0, right);
        for (left=1; left<=right; left+=1) {
            value = values[left];
            s = structs[left];
            for (j=left - 1; j >= 0 && values[j] >= value; j-=1) {
                values[j + 1] = values[j];
                structs[j + 1] = structs[j];
            }
            values[j + 1] = value;
            structs[j + 1] = s;
        }
}

private UnitArray UnitArrayUnits;

private function PopulateUnitArray() {
    UnitArrayUnits[bj_groupCountUnits] = GetEnumUnit();
    bj_groupCountUnits = bj_groupCountUnits + 1;
}

private function Group2UnitArray(group g) -> UnitArray {
        UnitArrayUnits = UnitArray.create(0);
        bj_groupCountUnits = 0;
        ForGroup(g, function PopulateUnitArray);
        UnitArrayUnits.size = bj_groupCountUnits;
    return UnitArrayUnits;
}

private function SortUnits(UnitArray objects, SortUnitFunc sort) {
    integer i = objects.size - 1;
        if (SortUtils_Values != 0) {
            RealArray(SortUtils_Values).destroy();
        }
        SortUtils_Values = RealArray.create(i + 1);
        for (i=i; i>=0; i-=1) {
            RealArray(SortUtils_Values)[i] = sort.evaluate(objects[i]);
        }
        SortUnitsByValues(objects, SortUtils_Values);
}
private function SortStructs(StructArray objects, SortStructFunc sort) {
    integer i = objects.size - 1;
        if (SortUtils_Values != 0) {
            RealArray(SortUtils_Values).destroy();
        }
        SortUtils_Values = RealArray.create(i + 1);
        for (i = i; i>=0; i-=1) {
            RealArray(SortUtils_Values)[i] = sort.evaluate(objects[i]);
        }
        SortStructsByValues(objects, SortUtils_Values);
}
//! endtextmacro

////! runtextmacro SortUtils("","MaxArraySize")
////! textmacro SortUtils takes SCOPE,SIZE
//$SCOPE$ struct RealArray
//    private real array values[$SIZE$]
//    integer size = 0
//
//    static method create takes integer size returns RealArray
//        local RealArray this
//            if size > $SIZE$ then
//                call BJDebugMsg("SortUtils error: Attempted to create RealArray with size larger than "+I2S($SIZE$)+".")
//                return 0
//            endif
//            set this = .allocate()
//            set .size = size
//        return this
//    endmethod
//
//    method operator []= takes integer i, real value returns nothing
//        set .values[i] = value
//    endmethod
//
//    method operator [] takes integer i returns real
//        return .values[i]
//    endmethod
//
//    method onDestroy takes nothing returns nothing
//        local integer i = .size - 1
//            loop
//                exitwhen i < 0
//                set .values[i] = 0.
//                set i = i - 1
//            endloop
//            set .size = 0
//    endmethod
//endstruct
//
//$SCOPE$ struct UnitArray
//    private unit array values[$SIZE$]
//    integer size = 0
//
//    static method create takes integer size returns UnitArray
//        local UnitArray this
//            if size > $SIZE$ then
//                call BJDebugMsg("SortUtils error: Attempted to create UnitArray with size larger than "+I2S($SIZE$)+".")
//                return 0
//            endif
//            set this = .allocate()
//            set .size = size
//        return this
//    endmethod
//
//    method operator []= takes integer i, unit value returns nothing
//        set .values[i] = value
//    endmethod
//
//    method operator [] takes integer i returns unit
//        return .values[i]
//    endmethod
//
//    method onDestroy takes nothing returns nothing
//        local integer i = .size - 1
//            loop
//                exitwhen i < 0
//                set .values[i] = null
//                set i = i - 1
//            endloop
//            set .size = 0
//    endmethod
//endstruct
//
//$SCOPE$ struct StructArray
//    private integer array values[$SIZE$]
//    integer size = 0
//
//    static method create takes integer size returns StructArray
//        local StructArray this
//            if size > $SIZE$ then
//                call BJDebugMsg("SortUtils error: Attempted to create StructArray with size larger than "+I2S($SIZE$)+".")
//                return 0
//            endif
//            set this = .allocate()
//            set .size = size
//        return this
//    endmethod
//
//    method operator []= takes integer i, integer value returns nothing
//        set .values[i] = value
//    endmethod
//
//    method operator [] takes integer i returns integer
//        return .values[i]
//    endmethod
//
//    method onDestroy takes nothing returns nothing
//        local integer i = .size - 1
//            loop
//                exitwhen i < 0
//                set .values[i] = 0
//                set i = i - 1
//            endloop
//            set .size = 0
//    endmethod
//endstruct
//
//
//private function SortValues_Sub takes RealArray values, integer left, integer right returns nothing
//    local integer pivot = GetRandomInt(left, right)
//    local real value = values[pivot]
//    local integer i = left
//    local integer j = left
//    local real swap
//        set values[pivot] = values[right]
//        set values[right] = value
//        loop
//            exitwhen j >= right
//            if values[j] < value then
//                set swap = values[j]
//                set values[j] = values[i]
//                set values[i] = swap
//                set i = i + 1
//            endif
//            set j = j + 1
//        endloop
//        set values[right] = values[i]
//        set values[i] = value
//        if right - (i + 1) > SortUtils_InsertionSortThreshold then
//            call SortValues_Sub(values, i + 1, right)
//        endif
//        if (i - 1) - left > SortUtils_InsertionSortThreshold then
//            call SortValues_Sub(values, left, i - 1)
//        endif
//endfunction
//
//$SCOPE$ function SortValues takes RealArray values returns nothing
//    local integer left = 1
//    local integer right = values.size - 1
//    local real value
//    local integer j
//        call SortValues_Sub(values, 0, right)
//        loop
//            exitwhen left > right
//            set value = values[left]
//            set j = left - 1
//            loop
//                exitwhen j < 0 or values[j] < value
//                set values[j + 1] = values[j]
//                set j = j - 1
//            endloop
//            set values[j + 1] = value
//            set left = left + 1
//        endloop
//endfunction
//
//
//private function SortUnitsByValues_Sub takes UnitArray units, RealArray values, integer left, integer right returns nothing
//    local integer pivot = GetRandomInt(left, right)
//    local real value = values[pivot]
//    local unit u = units[pivot]
//    local integer i = left
//    local integer j = left
//    local real swap
//    local unit swapu
//        set values[pivot] = values[right]
//        set units[pivot] = units[right]
//        set values[right] = value
//        set units[right] = u
//        loop
//            exitwhen j >= right
//            if values[j] < value then
//                set swap = values[j]
//                set swapu = units[j]
//                set values[j] = values[i]
//                set units[j] = units[i]
//                set values[i] = swap
//                set units[i] = swapu
//                set i = i + 1
//            endif
//            set j = j + 1
//        endloop
//        set values[right] = values[i]
//        set units[right] = units[i]
//        set values[i] = value
//        set units[i] = u
//        if right - (i + 1) > SortUtils_InsertionSortThreshold then
//            call SortUnitsByValues_Sub(units, values, i + 1, right)
//        endif
//        if (i - 1) - left > SortUtils_InsertionSortThreshold then
//            call SortUnitsByValues_Sub(units, values, left, i - 1)
//        endif
//    set u = null
//    set swapu = null
//endfunction
//
//$SCOPE$ function SortUnitsByValues takes UnitArray units, RealArray values returns nothing
//    local integer left = 1
//    local integer right = values.size - 1
//    local real value
//    local unit u
//    local integer j
//        call SortUnitsByValues_Sub(units, values, 0, right)
//        loop
//            exitwhen left > right
//            set value = values[left]
//            set u = units[left]
//            set j = left - 1
//            loop
//                exitwhen j < 0 or values[j] < value
//                set values[j + 1] = values[j]
//                set units[j + 1] = units[j]
//                set j = j - 1
//            endloop
//            set values[j + 1] = value
//            set units[j + 1] = u
//            set left = left + 1
//        endloop
//endfunction
//
//
//private function SortStructsByValues_Sub takes StructArray structs, RealArray values, integer left, integer right returns nothing
//    local integer pivot = GetRandomInt(left, right)
//    local real value = values[pivot]
//    local integer s = structs[pivot]
//    local integer i = left
//    local integer j = left
//    local real swap
//    local integer swaps
//        set values[pivot] = values[right]
//        set structs[pivot] = structs[right]
//        set values[right] = value
//        set structs[right] = s
//        loop
//            exitwhen j >= right
//            if values[j] < value then
//                set swap = values[j]
//                set swaps = structs[j]
//                set values[j] = values[i]
//                set structs[j] = structs[i]
//                set values[i] = swap
//                set structs[i] = swaps
//                set i = i + 1
//            endif
//            set j = j + 1
//        endloop
//        set values[right] = values[i]
//        set structs[right] = structs[i]
//        set values[i] = value
//        set structs[i] = s
//        if right - (i + 1) > SortUtils_InsertionSortThreshold then
//            call SortStructsByValues_Sub(structs, values, i + 1, right)
//        endif
//        if (i - 1) - left > SortUtils_InsertionSortThreshold then
//            call SortStructsByValues_Sub(structs, values, left, i - 1)
//        endif
//endfunction
//
//$SCOPE$ function SortStructsByValues takes StructArray structs, RealArray values returns nothing
//    local integer left = 1
//    local integer right = values.size - 1
//    local real value
//    local integer s
//    local integer j
//        call SortStructsByValues_Sub(structs, values, 0, right)
//        loop
//            exitwhen left > right
//            set value = values[left]
//            set s = structs[left]
//            set j = left - 1
//            loop
//                exitwhen j < 0 or values[j] < value
//                set values[j + 1] = values[j]
//                set structs[j + 1] = structs[j]
//                set j = j - 1
//            endloop
//            set values[j + 1] = value
//            set structs[j + 1] = s
//            set left = left + 1
//        endloop
//endfunction
//
//
//globals
//    private UnitArray UnitArrayUnits
//endglobals
//
//private function PopulateUnitArray takes nothing returns nothing
//    set UnitArrayUnits[bj_groupCountUnits] = GetEnumUnit()
//    set bj_groupCountUnits = bj_groupCountUnits + 1
//endfunction
//
//$SCOPE$ function Group2UnitArray takes group g returns UnitArray
//        set UnitArrayUnits = UnitArray.create(0)
//        set bj_groupCountUnits = 0
//        call ForGroup(g, function PopulateUnitArray)
//        set UnitArrayUnits.size = bj_groupCountUnits
//    return UnitArrayUnits
//endfunction
//
//
//$SCOPE$ function SortUnits takes UnitArray objects, SortUnitFunc sort returns nothing
//    local integer i = objects.size - 1
//        if SortUtils_Values != 0 then
//            call RealArray(SortUtils_Values).destroy()
//        endif
//        set SortUtils_Values = RealArray.create(i + 1)
//        loop
//            exitwhen i < 0
//            set RealArray(SortUtils_Values)[i] = sort.evaluate(objects[i])
//            set i = i - 1
//        endloop
//        call SortUnitsByValues(objects, SortUtils_Values)
//endfunction
//$SCOPE$ function SortStructs takes StructArray objects, SortStructFunc sort returns nothing
//    local integer i = objects.size - 1
//        if SortUtils_Values != 0 then
//            call RealArray(SortUtils_Values).destroy()
//        endif
//        set SortUtils_Values = RealArray.create(i + 1)
//        loop
//            exitwhen i < 0
//            set RealArray(SortUtils_Values)[i] = sort.evaluate(objects[i])
//            set i = i - 1
//        endloop
//        call SortStructsByValues(objects, SortUtils_Values)
//endfunction
////! endtextmacro
endlibrary